package com.wo.cloud.auth.sms;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.wo.cloud.auth.util.SmsConstant;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidGrantException;
import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AbstractTokenGranter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;

import java.util.Map;

/**
 * @author liry
 * @version 1.0
 * @date Created on 2021/5/25 10:12
 * Description: 手机号登录 tokengranter
 */
public class SmsCodeTokenGranter extends AbstractTokenGranter {

    private RedisTemplate<String, Object> redisTemplate;

    private AuthenticationManager authenticationManager;

    public SmsCodeTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
                               ClientDetailsService clientDetailsService, OAuth2RequestFactory oAuth2RequestFactory,
                               RedisTemplate redisTemplate) {
        this(authenticationManager, tokenServices, clientDetailsService, oAuth2RequestFactory, SmsConstant.GRANT_TYPE);
        this.redisTemplate = redisTemplate;
    }

    public SmsCodeTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,
                               ClientDetailsService clientDetailsService, OAuth2RequestFactory oAuth2RequestFactory,
                               String granterType) {
        super(tokenServices, clientDetailsService, oAuth2RequestFactory, granterType);
        this.authenticationManager = authenticationManager;
    }


    /**
     * 获取认证信息
     *
     * @param client
     * @param tokenRequest
     * @return
     */
    @Override
    protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

        // 校验用户 手机号验证码 信息
        Map<String, String> requestParameters = tokenRequest.getRequestParameters();
        String mobile = requestParameters.get(SmsConstant.MOBILE);
        String code = requestParameters.get(SmsConstant.CODE);

        // 判断code 是否存在
        if (StrUtil.isBlank(code)) {
            throw new UserDeniedAuthorizationException("请输入验证码");
        }

        String codeFromRedis = null;
        try {
            // 从redis中获取验证码
            codeFromRedis = (String) redisTemplate.opsForValue().get(SmsConstant.SMS_CODE_KEY + mobile);
            System.out.println(codeFromRedis);
        } catch (Exception e) {
            throw new UserDeniedAuthorizationException("验证码不存在");
        }

        if (StrUtil.isBlank(codeFromRedis)) {
            throw new UserDeniedAuthorizationException("验证码已过期");
        }


        // 判断验证码是否正确
        if (!StrUtil.equalsIgnoreCase(code, codeFromRedis)) {
            throw new UserDeniedAuthorizationException("验证码不正确");
        }

        // 删除 code
        redisTemplate.delete(mobile);

        // 获取 token
        Authentication userAuthentication = new SmsCodeAuthenticationToken(mobile);
        ((AbstractAuthenticationToken) userAuthentication).setDetails(requestParameters);


        // 进行认证
        try {
            userAuthentication = authenticationManager.authenticate(userAuthentication);
        } catch (AccountStatusException | BadCredentialsException ase) {
            //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)
            throw new InvalidGrantException(ase.getMessage());
        }

        // If the username/password are wrong the spec says we should send 400/invalid grant
        if (ObjectUtil.isNull(userAuthentication) || !userAuthentication.isAuthenticated()) {
            throw new InvalidGrantException("Could not authenticate user: " + mobile);
        }

        OAuth2Request oAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);

        return new OAuth2Authentication(oAuth2Request, userAuthentication);
    }
}
